package com.sy.common.persistence;

import com.sy.common.model.BaseModel;
import com.sy.common.model.ReadOnlyBaseModel;
import com.sy.common.persistence.QueryHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.math.BigInteger;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Created by zhuwenquan on 2017-10-29.
 */
@Slf4j
@Component
@Transactional
public class BaseDao {

    protected EntityManager em;

    protected String hideDeleted(String qStr) {
        return qStr + " AND deleted IS NULL";
    }

    @PersistenceContext(unitName = "default")
    public void setEm(EntityManager em) {
        this.em = em;
    }

    public EntityManager getEm() {
        return em;
    }

    public <T> T create(final T model) {
        this.em.persist(model);
        if(model instanceof ReadOnlyBaseModel){
            log.info("created model {} id {}", model.getClass().getSimpleName(), ((ReadOnlyBaseModel) model).getId());
        }
        return model;
    }

    public <T> T getById(Class<T> modelClass, final Object id) {
        if(id instanceof Integer){
            return this.em.find(modelClass, ((Integer) id).longValue());
        }else {
            return this.em.find(modelClass, id);
        }
    }

    public <T> T getByIdForWrite(Class<T> modelClass, final Object id) {
        return this.em.find(modelClass, id, LockModeType.PESSIMISTIC_WRITE);
    }

    public <T> T getOneByFieldValue(Class<T> modelClass, String fieldName, Object value) {
        return getOneByFieldValue(modelClass, fieldName, value, true, false);
    }

    public <T> T getOneByFieldValue(Class<T> modelClass, String fieldName, Object value, boolean hideDeleted) {
        return getOneByFieldValue(modelClass, fieldName, value, hideDeleted, false);
    }

    public <T> T getOneByFieldValueForWrite(Class<T> modelClass, String fieldName, Object value) {
        return getOneByFieldValue(modelClass, fieldName, value, true, true);
    }

    public <T> T getOneByFieldValue(Class<T> modelClass, String fieldName, Object value, boolean hideDeleted, boolean pessimisticWrite) {
        String tblName = modelClass.getSimpleName();
        String colName = fieldName;
        String qStr = "SELECT t FROM " + tblName + " t WHERE t." + colName + "= ?1";
        if (hideDeleted) {
            qStr = hideDeleted(qStr);
        }

        Query query = this.em.createQuery(qStr);
        query.setParameter(1, value);

        if (pessimisticWrite) {
            query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
        }

        try {
            return (T) query.getSingleResult();
        } catch (javax.persistence.NoResultException e) {
            return null;
        }
    }
    /**
     * 使用"in"的方式传入hql语句查询
     * @param hql
     * @param modelClass
     * @param list
     * @return
     */
    public <T> List<T> getAllByHQL(String hql, Class<T> modelClass, List list){
        Query query = this.em.createQuery(hql);
        query.setParameter("list", list);
        return query.getResultList();
    }

    public <T> List<T> getAllByList(Class<T> modelClass, String fieldName, List list, boolean hideDeleted) {
        String qStr = "FROM " + modelClass.getSimpleName() + " WHERE " + fieldName + " IN (:list)";
        if (hideDeleted) {
            qStr = hideDeleted(qStr);
        }
        Query query = this.em.createQuery(qStr);
        query.setParameter("list", list);
        return query.getResultList();
    }

    public <T extends BaseModel> void safeDelete(T model) {
//        model.setDeleted(new Date());
        this.em.merge(model);
    }

    public Integer executeUpdate(final String hql, Object... params) {
        Query query = this.em.createQuery(hql);
        for(int i = 0 ; i < params.length ; i++) {
            query.setParameter(i+1, params[i]);
        }
        return query.executeUpdate();
    }

    public <T> List<T> nativeQuery(final String sql, Class<T> modelClass) {
        String uc = sql.toUpperCase();
        if (StringUtils.countMatches(uc, "SELECT") >= 2) {
//            log.warn("{}: sub queries - {}", CBErrorCode.CASHBUSW_DB_0002A, sql);
        }

        Query query = this.em.createNativeQuery(sql, modelClass);
        long ts1 = System.currentTimeMillis();
        List<T> list = query.getResultList();
        long ts2 = System.currentTimeMillis();
        long diff = ts2 - ts1;
        if (diff >= 30000) {
//            log.warn("{}: long nativeQuery {} - {}", CBErrorCode.CASHBUSW_DB_0001C, diff, sql);
        } else if (diff >= 10000) {
//            log.warn("{}: long nativeQuery {} - {}", CBErrorCode.CASHBUSW_DB_0001B, diff, sql);
        } else if (diff >= 5000) {
//            log.warn("{}: long nativeQuery {} - {}", CBErrorCode.CASHBUSW_DB_0001A, diff, sql);
        }
        return list;
    }

    public void nativeUpdate(final String sql) {
        this.em.createNativeQuery(sql).executeUpdate();
    }

    public void nativeUpdate(final String sql, Object... params) {
        Query query = this.em.createNativeQuery(sql);
        for(int i = 0 ; i < params.length ; i++) {
            query.setParameter(i+1, params[i]);
        }
        query.executeUpdate();
    }

    public void nativeUpdate(final String sql, Map<String, Object> paramMap) {
        Query query = this.em.createNativeQuery(sql);
        for(String key: paramMap.keySet()) {
            query.setParameter(key, paramMap.get(key));
        }
        query.executeUpdate();
    }

    public List<Object[]> nativeQuery(final String sql) {
        Query query = this.em.createNativeQuery(sql);
        return query.getResultList();
    }

    public List<Object> nativeQuerySingleColumn(final String sql) {
        Query query = this.em.createNativeQuery(sql);
        return query.getResultList();
    }

    public List<Object> nativeQuerySingleColumn(final String sql, QueryHelper queryHelper) {
        Query query = this.em.createNativeQuery(sql);
        if (queryHelper != null) {
            query.setFirstResult(queryHelper.getOffset());
            query.setMaxResults(queryHelper.getLimit());
        }
        return query.getResultList();
    }

    public int nativeQueryGetCount(String sql) {
        List<Object> list = nativeQuerySingleColumn(sql);
        BigInteger cnt = (BigInteger) list.get(0);
        return cnt.intValue();
    }

    public <T> List<T> listQuery(final String hql, Class<T> modelClass) {
        Query query = this.em.createQuery(hql, modelClass);
        return query.getResultList();
    }

    public <T> List<T> listQuery(final String hql, Class<T> modelClass, QueryHelper queryHelper) {
        Query query = this.em.createQuery(hql, modelClass);
        if (queryHelper != null) {
            query.setFirstResult(queryHelper.getOffset());
            query.setMaxResults(queryHelper.getLimit());
        }
        return query.getResultList();
    }

    public <T> List<T> listQuery(final String hql, Class<T> modelClass, Object... params) {
        Query query = this.em.createQuery(hql, modelClass);
        for(int i = 0 ; i < params.length ; i++) {
            query.setParameter(i+1, params[i]);
        }
        return query.getResultList();
    }

    public <T> List<T> listQuery(final String hql, Class<T> modelClass, List<Object> params) {
        Query query = this.em.createQuery(hql, modelClass);
        for(int i = 0 ; i < params.size() ; i++) {
            query.setParameter(i+1, params.get(i));
        }
        return query.getResultList();
    }



    public <T> List<T> find(final String hql, final int page, final int rows, Class<T> modelClass, Object... params) {
        Query q = this.em.createQuery(hql, modelClass);
        for(int i = 0 ; i < params.length ; i++) {
            q.setParameter(i, params[i]);
        }
        return q.setFirstResult((page - 1) * rows).setMaxResults(rows).getResultList();
    }

    public <T> T singleQuery(final String hql, Class<T> modelClass) {
        Query query = this.em.createQuery(hql, modelClass);
        try {
            return (T) query.getSingleResult();
        } catch (javax.persistence.NoResultException e) {
            return null;
        }
        /*
        if(query.getResultList().size() == 0 ){
        	return null;
        } else {
        	return (T) query.getSingleResult();
        }
        */
    }

    public <T> T singleQuery(final String hql, Class<T> modelClass, Object... params) {
        Query query = this.em.createQuery(hql, modelClass);
        for(int i = 0 ; i < params.length ; i++) {
            query.setParameter(i+1, params[i]);
        }
        try {
            return (T) query.getSingleResult();
        } catch (javax.persistence.NoResultException e) {
            return null;
        }
        /*
        if(query.getResultList().size() == 0 ){
            return null;
        } else {
            return (T) query.getSingleResult();
        }
        */
    }
    public <T> T queryObject(final String hql, Class<T> modelClass, Object... params) {
        Query query = this.em.createQuery(hql, modelClass);
        for(int i = 0 ; i < params.length ; i++) {
            query.setParameter(i+1, params[i]);
        }
        //query.setFirstResult(queryHelper.getOffset());
        query.setMaxResults(1);
        if(query.getResultList().size() == 0 ){
            return null;
        } else {
            return (T) query.getSingleResult();
        }
    }
    public <T> int getCountByFieldValue(Class<T> modelClass, String fieldName, Object value) {
        return getCountByFieldValue(modelClass, fieldName, value, true);
    }

    public <T> int getCountByFieldValue(Class<T> modelClass, String fieldName, Object value, boolean hideDeleted) {
        String tblName = modelClass.getSimpleName();
        String colName = fieldName;
        String qStr = "SELECT COUNT(*) FROM " + tblName + " t WHERE t." + colName + "= :" + colName;
        if (hideDeleted) {
            qStr = hideDeleted(qStr);
        }
        Query query = this.em.createQuery(qStr);
        query.setParameter(colName, value);
        Number count = (Number) query.getSingleResult();
        return count.intValue();
    }

    public <T> T update(final T model) {
        assert(model instanceof BaseModel);
        if (model instanceof BaseModel) {
            log.info("updated model {} id {}", model.getClass().getSimpleName(), ((BaseModel) model).getId());
//            log.info("updated model {} id {} version {}", model.getClass().getSimpleName(), ((BaseModel) model).getId(), ((BaseModel) model).getVersion());
        } else if (model instanceof ReadOnlyBaseModel) {
            log.info("updated model {} id {}", model.getClass().getSimpleName(), ((ReadOnlyBaseModel) model).getId());
        }
        return this.em.merge(model);
    }

    public <T> void remove(final T model) {
        if (model instanceof ReadOnlyBaseModel) {
            log.info("deleted model {} id {}", model.getClass().getSimpleName(), ((ReadOnlyBaseModel) model).getId());
        }
        this.em.remove(this.em.merge(model));
    }

    public void flush() {
        this.em.flush();
    }

    public void clear() {
        this.em.clear();
    }

    public <T> List<T> listQueryForPage(final String hql, Class<T> modelClass, QueryHelper queryHelper) {
        Query query = this.em.createQuery(hql, modelClass);
        if (queryHelper != null) {
            query.setFirstResult(queryHelper.getOffset());
            query.setMaxResults(queryHelper.getLimit());
        }
        return query.getResultList();
    }

    public <T> List<T> listQueryLimit(final String hql, Class<T> modelClass, QueryHelper queryHelper, int start, int top) {
        Query query = this.em.createQuery(hql, modelClass);
        if (queryHelper != null) {
            query.setFirstResult(start);
            query.setMaxResults(top);
        }
        return query.getResultList();
    }

    public int prc_call(final String sql)
    {
        //Query query = this.em.createNativeQuery("{call cashbus_etl.prc_test('123456')}");

        Query query = this.em.createNativeQuery(sql);
        //	StoredProcedureQuery sp =this.em.createStoredProcedureQuery(sql);
        //	sp.setParameter(1, "13636510624");
        //	sp.setParameter(2, 5);
        //	if( sp.execute())
        //	return 1;
        //else return 0;
        return query.executeUpdate();

    }

}
